/* sasl.c
** strophe XMPP client library -- SASL authentication helpers
** 
** Copyright (C) 2005-2009 Collecta, Inc. 
**
**  This software is provided AS-IS with no warranty, either express
**  or implied.
**
**  This software is distributed under license and may not be copied,
**  modified or distributed except as expressly authorized under the
**  terms of the license contained in the file LICENSE.txt in this
**  distribution.
*/

/** @file
 *  SASL authentication.
 */
#include "xsapi.h"

#include "xssasl.h"
#include "xsmd5.h"
#include "xshash.h"

/** generate authentication string for the SASL PLAIN mechanism */
char *sasl_plain(const char *authid, const char *password) 
{
  int idlen, passlen;
  char *result = 0;
  char *msg = 0;
  
  /* our message is Base64(authzid,\0,authid,\0,password)
     if there is no authzid, that field is left empty */

  idlen = strlen(authid);
  passlen = strlen(password);
  xs_malloc(char *, msg, (2 + idlen + passlen));
  if (msg != 0) {
		msg[0] = '\0';
		memcpy(msg+1, authid, idlen);
		msg[1+idlen] = '\0';
		memcpy(msg+1+idlen+1, password, passlen);
		result = xs_base64_encode((unsigned char *)msg, 2 + idlen + passlen);
		xs_free(msg);
  }

  return result;
}

/** helpers for digest auth */

/* create a new, 0-terminated string from a substring */
static char *_make_string(const char *s, const unsigned len)
{
    char *result;

    xs_malloc(char *, result, len + 1);
    if (result != 0) {
			memcpy(result, s, len);
			result[len] = '\0';
    }
    return result;
}

/* create a new, 0-terminated string quoting another string */
static char *_make_quoted(const char *s)
{
    char *result;
    int len = strlen(s);

    xs_malloc(char *, result, len + 3);
    if (result != 0) {
			result[0] = '"';
			memcpy(result+1, s, len);
			result[len+1] = '"';
			result[len+2] = '\0';
    }
    return result;
}

/* split key, value pairs into a hash */
hash_t *_parse_digest_challenge(const char *msg)
{
    hash_t *result;
    unsigned char *text;
    char *key, *value;
    unsigned char *s, *t;

    text = xs_base64_decode(msg, strlen(msg));
    if (text == 0) {
			return 0;
    }
		xs_print("Challenge:%s\n\r", text);
    result = hash_new(10);
    if (result != 0) {
			s = text;
			while (*s != '\0') {
		    /* skip any leading commas and spaces */
		    while ((*s == ',') || (*s == ' ')) s++;
		    /* accumulate a key ending at '=' */
		    t = s;
		    while ((*t != '=') && (*t != '\0')) t++;
		    if (*t == '\0') break; /* bad string */
		    key = _make_string((char *)s, (t-s));
		    if (key == 0) break;
	            /* advance our start pointer past the key */
		    s = t + 1;
		    t = s;
		    /* if we see quotes, grab the string in between */
		    if ((*s == '\'') || (*s == '"')) {
					t++;
					while ((*t != *s) && (*t != '\0'))
					    t++;
					value = _make_string((char *)s+1, (t-s-1));
					if (*t == *s) {
					    s = t + 1;
					} else {
					    s = t;
					}
				    /* otherwise, accumulate a value ending in ',' or '\0' */
		    } else {
					while ((*t != ',') && (*t != '\0')) t++;
					value = _make_string((char *)s, (t-s));
					s = t;
		    }
		    if (value == 0) {
					xs_free(key);
					break;
		    }
		    /* TODO: check for collisions per spec */
		    hash_add(result, key, value);
		    /* hash table now owns the value, free the key */
		    xs_free(key);
			}
    }
    xs_free(text);

    return result;
}

/** expand a 16 byte MD5 digest to a 32 byte hex representation */
void _digest_to_hex(const char *digest, char *hex)
{
  int i;
  const char hexdigit[] = "0123456789abcdef";

  for (i = 0; i < 16; i++) {
		*hex++ = hexdigit[ (digest[i] >> 4) & 0x0F ];
		*hex++ = hexdigit[ digest[i] & 0x0F ];
  }
}

/** append 'key="value"' to a buffer, growing as necessary */
char *_add_key(hash_t *table, const char *key, 
		      char *buf, int *len, int quote)
{
  int olen,nlen;
  int keylen, valuelen;
  const char *value, *qvalue;
  char *c;

  /* allocate a zero-length string if necessary */
  if (buf == 0) {
		xs_malloc(char *, buf, 1);
		buf[0] = '\0';
  }
  if (buf == 0) return 0;

  /* get current string length */
  olen = strlen(buf);
  value = hash_get(table, key);
  if (value == 0) {
		xs_print("SASL couldn't retrieve value for '%s'", key);
		value = "";
  }
  if (quote) {
		qvalue = _make_quoted(value);
  } else {
		qvalue = value;
  }
  /* added length is key + '=' + value */
  /*   (+ ',' if we're not the first entry   */
  keylen = strlen(key);
  valuelen = strlen(qvalue);
  nlen = (olen ? 1 : 0) + keylen + 1 + valuelen + 1;
	xs_realloc(char *, buf, olen+nlen);

  if (buf != 0) {
		c = buf + olen;
		if (olen) *c++ = ',';
		memcpy(c, key, keylen); c += keylen;
		*c++ = '=';
		memcpy(c, qvalue, valuelen); c += valuelen;
		*c++ = '\0';
  }

  if (quote) xs_free((char *)qvalue);

  return buf;
}
#ifdef NO_OS
#define	SASL_BUF_SIZE  256
char s_acResponse[SASL_BUF_SIZE] = {0};
char s_acChallenge[SASL_BUF_SIZE] = {0};

char *sasl_digest_md5(const char *challenge, char *node, char *domain, const char *password) 
{
	char *pcText = 0, *pcNonce = 0;
	unsigned char ucNonceLen = 0;
	struct MD5Context MD5;
	unsigned char aucTmp[64] = {0};
	unsigned char aucNonceTmp[64] = {0};
	unsigned char digest[16], HA1[16], HA2[16];
	char hex[32];
	char *pcChallenge = 0;
	xs_print("MD5 encode in no os\n\r");
	pcText = (char *)xs_base64_decode(challenge, strlen(challenge));
  if (0 == pcText) {
		return 0;
  }
	pcNonce = strstr(pcText, "nonce");
	if(!pcNonce){
		return 0;
	}
	pcNonce += 7;
	for(ucNonceLen=0; ucNonceLen<strlen(pcText); ucNonceLen++){
		if('"' == pcNonce[ucNonceLen]){
			break;
		}
	}
	memcpy(aucNonceTmp, pcNonce, ucNonceLen);
	
	/* construct MD5(node : domain : password) */
	MD5Init(&MD5);
	MD5Update(&MD5, (unsigned char *)node, strlen(node));
	MD5Update(&MD5, (unsigned char *)":", 1);
	MD5Update(&MD5, (unsigned char *)domain, strlen(domain));
	MD5Update(&MD5, (unsigned char *)":", 1);
	MD5Update(&MD5, (unsigned char *)password, strlen(password));
	MD5Final(digest, &MD5);

	/* digest now contains the first field of A1 */
	MD5Init(&MD5);
	MD5Update(&MD5, digest, 16);
	MD5Update(&MD5, (unsigned char *)":", 1);
	MD5Update(&MD5, aucNonceTmp, ucNonceLen); // nonce
	MD5Update(&MD5, (unsigned char *)":", 1);
	sprintf((char *)aucTmp, "00DEADBEEF00");
	MD5Update(&MD5, aucTmp, strlen((char *)aucTmp));// cnonce
	MD5Final(digest, &MD5);

	/* now digest is MD5(A1) */
	memcpy(HA1, digest, 16);

	/* construct MD5(A2) */
	MD5Init(&MD5);
	MD5Update(&MD5, (unsigned char *)"AUTHENTICATE:", 13);
	memset(aucTmp, 0, sizeof(aucTmp));
	sprintf((char *)aucTmp, "xmpp/%s", domain); // digest-uri
	MD5Update(&MD5, (unsigned char *)aucTmp, strlen((char *)aucTmp));
	MD5Final(digest, &MD5);

	memcpy(HA2, digest, 16);

	/* construct response */
	MD5Init(&MD5);
	_digest_to_hex((char *)HA1, hex);
	MD5Update(&MD5, (unsigned char *)hex, 32);
 	MD5Update(&MD5, (unsigned char *)":", 1);
	MD5Update(&MD5,  aucNonceTmp, ucNonceLen); // nonce
	MD5Update(&MD5, (unsigned char *)":", 1);
	memset(aucTmp, 0, sizeof(aucTmp));
	sprintf((char *)aucTmp, "00000001");            // nc
	MD5Update(&MD5, aucTmp, strlen((char *)aucTmp));
	MD5Update(&MD5, (unsigned char *)":", 1);
	memset(aucTmp, 0, sizeof(aucTmp));
	sprintf((char *)aucTmp, "00DEADBEEF00");
	MD5Update(&MD5, aucTmp, strlen((char *)aucTmp));// cnonce
	MD5Update(&MD5, (unsigned char *)":", 1);
	memset(aucTmp, 0, sizeof(aucTmp));
	sprintf((char *)aucTmp, "auth");
	MD5Update(&MD5, aucTmp, strlen((char *)aucTmp));// auth
	MD5Update(&MD5, (unsigned char *)":", 1);
	_digest_to_hex((char *)HA2, hex);
	MD5Update(&MD5, (unsigned char *)hex, 32);
	MD5Final(digest, &MD5);

	_digest_to_hex((char *)digest, hex);
	memset(aucTmp, 0, sizeof(aucTmp));
	memcpy(aucTmp, hex, 32);
	snprintf(s_acResponse, SASL_BUF_SIZE, "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"00DEADBEEF00\",nc=00000001,qop=auth,digest-uri=\"xmpp/%s\",response=%s,charset=utf-8", \
		node, domain, aucNonceTmp, domain, aucTmp);

	pcChallenge = xs_base64_encode((unsigned char *)s_acResponse, strlen(s_acResponse));
	return pcChallenge;
}
#else
/** generate auth response string for the SASL DIGEST-MD5 mechanism */
char *sasl_digest_md5(const char *challenge, char *node, char *domain, const char *password) 
{
	hash_t *table;
	char *result = 0;
	char *realm;
	char *value;
	char *response;
	int rlen;
	struct MD5Context MD5;
	unsigned char digest[16], HA1[16], HA2[16];
	char hex[32];

	/* parse the challenge */
	table = _parse_digest_challenge(challenge);
	if (table == 0) {
		xs_print("SASL couldn't parse digest challenge");
		return 0;
	}
	/* generate default realm of domain if one didn't come from the
	   server */
	realm = hash_get(table, "realm");
	if (realm == 0 || strlen(realm) == 0) {
		hash_add(table, "realm", hash_strdup(domain));
		realm = hash_get(table, "realm");
	}

	/* add our response fields */
	hash_add(table, "username", hash_strdup(node));
	/* TODO: generate a random cnonce */
	hash_add(table, "cnonce", hash_strdup("00DEADBEEF00"));
	hash_add(table, "nc", hash_strdup("00000001"));
	hash_add(table, "qop", hash_strdup("auth"));
  xs_malloc(char *, value, 5 + strlen(domain) + 1);
	memcpy(value, "xmpp/", 5);
	memcpy(value+5, domain, strlen(domain));
	value[5+strlen(domain)] = '\0';
	hash_add(table, "digest-uri", value);

	/* generate response */

	/* construct MD5(node : realm : password) */
	MD5Init(&MD5);
	MD5Update(&MD5, (unsigned char *)node, strlen(node));
	MD5Update(&MD5, (unsigned char *)":", 1);
	MD5Update(&MD5, (unsigned char *)realm, strlen(realm));
	MD5Update(&MD5, (unsigned char *)":", 1);
	MD5Update(&MD5, (unsigned char *)password, strlen(password));
	MD5Final(digest, &MD5);

	/* digest now contains the first field of A1 */

	MD5Init(&MD5);
	MD5Update(&MD5, digest, 16);
	MD5Update(&MD5, (unsigned char *)":", 1);
	value = hash_get(table, "nonce");
	MD5Update(&MD5, (unsigned char *)value, strlen(value));
	MD5Update(&MD5, (unsigned char *)":", 1);
	value = hash_get(table, "cnonce");
	MD5Update(&MD5, (unsigned char *)value, strlen(value));
	MD5Final(digest, &MD5);

	/* now digest is MD5(A1) */
	memcpy(HA1, digest, 16);

	/* construct MD5(A2) */
	MD5Init(&MD5);
	MD5Update(&MD5, (unsigned char *)"AUTHENTICATE:", 13);
	value = hash_get(table, "digest-uri");
	MD5Update(&MD5, (unsigned char *)value, strlen(value));
	if (strcmp(hash_get(table, "qop"), "auth") != 0) {
		MD5Update(&MD5, (unsigned char *)":00000000000000000000000000000000", 33);
	}
	MD5Final(digest, &MD5);

	memcpy(HA2, digest, 16);

	/* construct response */
	MD5Init(&MD5);
	_digest_to_hex((char *)HA1, hex);
	MD5Update(&MD5, (unsigned char *)hex, 32);
	MD5Update(&MD5, (unsigned char *)":", 1);
	value = hash_get(table, "nonce");
	MD5Update(&MD5, (unsigned char *)value, strlen(value));
	MD5Update(&MD5, (unsigned char *)":", 1);
	value = hash_get(table, "nc");
	MD5Update(&MD5, (unsigned char *)value, strlen(value));
	MD5Update(&MD5, (unsigned char *)":", 1);
	value = hash_get(table, "cnonce");
	MD5Update(&MD5, (unsigned char *)value, strlen(value));
	MD5Update(&MD5, (unsigned char *)":", 1);
	value = hash_get(table, "qop");
	MD5Update(&MD5, (unsigned char *)value, strlen(value));
	MD5Update(&MD5, (unsigned char *)":", 1);
	_digest_to_hex((char *)HA2, hex);
	MD5Update(&MD5, (unsigned char *)hex, 32);
	MD5Final(digest, &MD5);

	xs_malloc(char *,response, 32+1);
	_digest_to_hex((char *)digest, hex);
	memcpy(response, hex, 32);
	response[32] = '\0';
	hash_add(table, "response", response);
	/* construct reply */
	result = 0;
	rlen = 0;
	result = _add_key(table, "username", result, &rlen, 1); 
	result = _add_key(table, "realm", result, &rlen, 1); 
	result = _add_key(table, "nonce", result, &rlen, 1); 
	result = _add_key(table, "cnonce", result, &rlen, 1); 
	result = _add_key(table, "nc", result, &rlen, 0); 
	result = _add_key(table, "qop", result, &rlen, 0); 
	result = _add_key(table, "digest-uri", result, &rlen, 1); 
	result = _add_key(table, "response", result, &rlen, 0); 
	result = _add_key(table, "charset", result, &rlen, 0);

	hash_release(table); /* also frees value strings */

	/* reuse response for the base64 encode of our result */
	response = xs_base64_encode((unsigned char *)result, strlen(result));

	xs_free(result);
	return response;
}
#endif

/** Base64 encoding routines. Implemented according to RFC 3548 */

/** map of all byte values to the base64 values, or to
    '65' which indicates an invalid character. '=' is '64' */
static const char _base64_invcharmap[256] = {
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,62, 65,65,65,63,
    52,53,54,55, 56,57,58,59, 60,61,65,65, 65,64,65,65,
    65, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
    15,16,17,18, 19,20,21,22, 23,24,25,65, 65,65,65,65,
    65,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
    41,42,43,44, 45,46,47,48, 49,50,51,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65,
    65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65 
};

/** map of all 6-bit values to their corresponding byte
    in the base64 alphabet. Padding char is the value '64' */
static const char _base64_charmap[65] = {
    'A','B','C','D', 'E','F','G','H',
    'I','J','K','L', 'M','N','O','P',
    'Q','R','S','T', 'U','V','W','X',
    'Y','Z','a','b', 'c','d','e','f',
    'g','h','i','j', 'k','l','m','n',
    'o','p','q','r', 's','t','u','v',
    'w','x','y','z', '0','1','2','3',
    '4','5','6','7', '8','9','+','/',
    '='
};

int base64_encoded_len(const unsigned len)
{
    /* encoded steam is 4 bytes for every three, rounded up */
    return ((len + 2)/3) << 2;
}

char *xs_base64_encode(const unsigned char * const buffer, const unsigned len)
{
    int clen;/* if defined NO_OS, here is warning */
    char *cbuf = 0, *c = 0;
    unsigned int word, hextet;
    int i;

    
	#ifdef NO_OS
	memset(s_acChallenge, 0, SASL_BUF_SIZE);
	cbuf = s_acChallenge;
	#else
	clen = base64_encoded_len(len);
	xs_malloc(char *, cbuf, clen + 1);
	#endif
	if (cbuf != 0) {
		c = cbuf;
		/* loop over data, turning every 3 bytes into 4 characters */
		for (i = 0; i < len - 2; i += 3) {
		word = buffer[i] << 16 | buffer[i+1] << 8 | buffer[i+2];
		hextet = (word & 0x00FC0000) >> 18;
		*c++ = _base64_charmap[hextet];
		hextet = (word & 0x0003F000) >> 12;
		*c++ = _base64_charmap[hextet];
		hextet = (word & 0x00000FC0) >> 6;
		*c++ = _base64_charmap[hextet];
		hextet = (word & 0x000003F);
		*c++ = _base64_charmap[hextet];
		}
		/* zero, one or two bytes left */
		switch (len - i) {
			case 0:
			break;
			case 1:
			hextet = (buffer[len-1] & 0xFC) >> 2;
			*c++ = _base64_charmap[hextet];
			hextet = (buffer[len-1] & 0x03) << 4;
			*c++ = _base64_charmap[hextet];
			*c++ = _base64_charmap[64]; /* pad */
			*c++ = _base64_charmap[64]; /* pad */
			break;
			case 2:
			hextet = (buffer[len-2] & 0xFC) >> 2;
			*c++ = _base64_charmap[hextet];
			hextet = ((buffer[len-2] & 0x03) << 4) |
				 ((buffer[len-1] & 0xF0) >> 4);
			*c++ = _base64_charmap[hextet];
			hextet = (buffer[len-1] & 0x0F) << 2;
			*c++ = _base64_charmap[hextet];
			*c++ = _base64_charmap[64]; /* pad */
			break;
		}
		/* add a terminal 0 */
		*c = '\0';
	}

    return cbuf;
}

int base64_decoded_len(const char * const buffer, const unsigned len)
{
	int nudge;
	int c;

	/* count the padding characters for the remainder */
	nudge = -1;
	c = _base64_invcharmap[(int)buffer[len-1]];
	if (c < 64) nudge = 0;
	else if (c == 64) {
		c = _base64_invcharmap[(int)buffer[len-2]];
		if (c < 64) nudge = 1;
		else if (c == 64) {
		  c = _base64_invcharmap[(int)buffer[len-3]];
		  if (c < 64) nudge = 2;
		} 
	}
	if (nudge < 0) return 0; /* reject bad coding */

	/* decoded steam is 3 bytes for every four */ 
	return 3 * (len >> 2) - nudge;
}

unsigned char *xs_base64_decode(const char * const buffer, const unsigned len)
{
    int dlen;
    unsigned char *dbuf = 0, *d = 0;
    unsigned int word = 0, hextet = 0;
    int i;

    /* len must be a multiple of 4 */
    if (len & 0x03) return 0;

    dlen = base64_decoded_len(buffer, len);
		#ifdef NO_OS
		memset(s_acResponse, 0, SASL_BUF_SIZE);
		dbuf = (unsigned char *)s_acResponse;
		#else
		xs_malloc(unsigned char *, dbuf, dlen + 1);
		#endif
    if (dbuf != 0) {
			d = dbuf;
			/* loop over each set of 4 characters, decoding 3 bytes */
			for (i = 0; i < len - 3; i += 4) {
			    hextet = _base64_invcharmap[(int)buffer[i]];
			    if (hextet & 0xC0) break;
			    word = hextet << 18;
			    hextet = _base64_invcharmap[(int)buffer[i+1]];
			    if (hextet & 0xC0) break;
			    word |= hextet << 12;
			    hextet = _base64_invcharmap[(int)buffer[i+2]];
			    if (hextet & 0xC0) break;
			    word |= hextet << 6;
			    hextet = _base64_invcharmap[(int)buffer[i+3]];
			    if (hextet & 0xC0) break;
			    word |= hextet;
			    *d++ = (word & 0x00FF0000) >> 16;
			    *d++ = (word & 0x0000FF00) >> 8;
			    *d++ = (word & 0x000000FF);
			}
			if (hextet > 64) goto _base64_decode_error;
			/* handle the remainder */
			switch (dlen % 3) {
			    case 0:
				/* nothing to do */
				break;
			    case 1:
				/* redo the last quartet, checking for correctness */
				hextet = _base64_invcharmap[(int)buffer[len-4]];
				if (hextet & 0xC0) goto _base64_decode_error;
				word = hextet << 2;
				hextet = _base64_invcharmap[(int)buffer[len-3]];
				if (hextet & 0xC0) goto _base64_decode_error;
				word |= hextet >> 4;
				*d++ = word & 0xFF;
				hextet = _base64_invcharmap[(int)buffer[len-2]];
				if (hextet != 64) goto _base64_decode_error;
				hextet = _base64_invcharmap[(int)buffer[len-1]];
				if (hextet != 64) goto _base64_decode_error;
				break;
			    case 2:
				/* redo the last quartet, checking for correctness */
				hextet = _base64_invcharmap[(int)buffer[len-4]];
				if (hextet & 0xC0) goto _base64_decode_error;
				word = hextet << 10;		
				hextet = _base64_invcharmap[(int)buffer[len-3]];
				if (hextet & 0xC0) goto _base64_decode_error;
				word |= hextet << 4;		
				hextet = _base64_invcharmap[(int)buffer[len-2]];
				if (hextet & 0xC0) goto _base64_decode_error;
				word |= hextet >> 2;
				*d++ = (word & 0xFF00) >> 8;
				*d++ = (word & 0x00FF);		
				hextet = _base64_invcharmap[(int)buffer[len-1]];
				if (hextet != 64) goto _base64_decode_error;
				break;
			}
    }
    *d = '\0';
    return dbuf;

_base64_decode_error:	
    /* invalid character; abort decoding! */
    return 0;
}

/*** self tests ***/
#ifdef TEST

#include <stdio.h>

int test_charmap_identity(void)
{
	int i, v, u;

	for (i = 0; i < 65; i++) {
		v = _base64_charmap[i];
		if (v > 255) return 1;
			u = _base64_invcharmap[v];
		/*	xs_print("map: %d -> %d -> %d\n", i, v, u); */
		if (u != i) return 1;
	}

	return 0; 
}

int test_charmap_range(void)
{
	int i, v;

	for (i = 64; i < 256; i++) {
		v = _base64_invcharmap[i];
		if (i < 64) return 1;
	}

	return 0;
}

int main(int argc, char *argv[])
{
	int ret = 0;

	xs_print("testing charmap identity...");
	ret = test_charmap_identity();
	if (ret) return ret;
	xs_print(" ok.\n");

	xs_print("testing charmap range...");
	ret = test_charmap_range();
	if (ret) return ret;
	xs_print(" ok.\n");

	xs_print("no error\n");
	return 0;
}

#endif /* TEST */

